package tools

import (
	"errors"
	"gozeroStandalone/internal/config"
	"gozeroStandalone/utils"
	"net/http"

	"github.com/zeromicro/go-zero/core/limit"
	"github.com/zeromicro/go-zero/core/stores/redis"
)

/*
 +----------------------------------------------------------------------
 + Title        : MultiLevelRateLimiter
 + Author       : huwl
 + Version      : 1.0.0
 + Initial-Time : 2025/11/04 22:36:33
 + Last-time    : 这个文件最后修改的时间 + 修改人的名称
 + Desc         : 多级限流器
 +----------------------------------------------------------------------
*/

type MultiLevelRateLimiter struct {
	globalLimiter   *limit.PeriodLimit  // 全局限流
	ipLimiter       *limit.PeriodLimit  // IP限流
	userLimiter     *limit.PeriodLimit  // 用户限流
	criticalLimiter *limit.TokenLimiter // 关键操作令牌桶限流
}

/**
 * 创建多级限流器
 *
 * @param config.Config conf ------------------------必传
 * @return *MultiLevelRateLimiter
 * @author huwl
 * @date 2025/11/04 22:36:33
 */
func NewMultiLevelRateLimiter(conf config.Config) *MultiLevelRateLimiter {
	// 组装redis配置
	rc := redis.RedisConf{}
	switch conf.Env {
	case "dev":
		rc.Host = conf.RedisConf.Dev.Single.Host
		rc.Pass = conf.RedisConf.Dev.Single.Password
	case "test":
		rc.Host = conf.RedisConf.Test.Single.Host
		rc.Pass = conf.RedisConf.Test.Single.Password
	case "prod":
		rc.Host = conf.RedisConf.Prod.Single.Host
		rc.Pass = conf.RedisConf.Prod.Single.Password
	}

	if conf.RedisConf.Mode == "cluster" {
		rc.Type = "cluster"
	} else {
		rc.Type = "node"
	}
	// 创建redis
	rds := redis.MustNewRedis(rc)

	return &MultiLevelRateLimiter{
		// 全局每秒1000次
		globalLimiter: limit.NewPeriodLimit(
			conf.LimiterConf.GlobalLimiter.Period,
			conf.LimiterConf.GlobalLimiter.Quota,
			rds, "global_limiter"),
		// 每个IP每分钟300次
		ipLimiter: limit.NewPeriodLimit(
			conf.LimiterConf.IpLimiter.Period,
			conf.LimiterConf.IpLimiter.Quota,
			rds, "ip_limiter"),
		// 每个用户每小时10000次
		userLimiter: limit.NewPeriodLimit(
			conf.LimiterConf.UserLimiter.Period,
			conf.LimiterConf.UserLimiter.Quota,
			rds, "user_limiter"),
		// 关键操作每秒10次，桶容量50
		criticalLimiter: limit.NewTokenLimiter(
			conf.LimiterConf.CriticalLimiter.Rate,
			conf.LimiterConf.CriticalLimiter.Burst,
			rds, "critical_limiter"),
	}
}

/**
 * 验证限流
 *
 * @param *http.Request r --------------------------必传
 * @param string uid -------------------------------必传
 * @return error
 * @author huwl
 * @date 2025/11/04 22:35:57
 */
func (m *MultiLevelRateLimiter) CheckRateLimit(r *http.Request, uid string) error {
	clientIP := utils.GetClientIP(r)

	// 1. 检查全局限流
	if code, _ := m.globalLimiter.Take("global"); code == limit.OverQuota {
		return errors.New("系统繁忙，请稍后重试")
	}

	// 2. 检查IP限流
	if code, _ := m.ipLimiter.Take(clientIP); code == limit.OverQuota {
		return errors.New("IP请求频率过高")
	}

	// 3. 检查用户限流（如果已登录）
	if uid != "" {
		if code, _ := m.userLimiter.Take("user_" + uid); code == limit.OverQuota {
			return errors.New("用户操作频率过高")
		}
	}

	// 4. 对关键操作使用令牌桶限流（允许突发）
	if isCriticalOperation(r) && !m.criticalLimiter.Allow() {
		return errors.New("关键操作过于频繁")
	}

	return nil
}

func isCriticalOperation(r *http.Request) bool {
	// 判断是否为关键操作
	return r.Method == http.MethodPost &&
		(r.URL.Path == "/api/payment" || r.URL.Path == "/api/order")
}
